package com.data.modules.openapi.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.data.common.exception.BootException;
import com.data.common.util.CmdUtils;
import com.data.common.util.RedisUtil;
import com.data.modules.openapi.config.OpenApiConfig;
import com.data.modules.openapi.entity.OpenApiApp;
import com.data.modules.openapi.entity.OpenApiAppPermission;
import com.data.modules.openapi.mapper.OpenApiAppMapper;
import com.data.modules.openapi.mapper.OpenApiAppPermissionMapper;
import com.data.modules.openapi.service.IOpenApiService;
import com.xkcoding.http.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import javax.servlet.http.HttpServletRequest;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

/**
 * @Description: open_api_app
 * @Author: data-boot
 * @Date:   2022-04-07
 * @Version: V1.0
 */
@SuppressWarnings("ALL")
@Slf4j
@Service
public class OpenApiServiceImpl implements IOpenApiService {
    @Autowired
    private OpenApiConfig openApiConfig;
    @Autowired
    private OpenApiAppMapper openApiAppMapper;
    @Autowired
    private OpenApiAppPermissionMapper openApiAppPermissionMapper;
    @Autowired
    private RedisUtil redisUtil;



    @Override
    public String openapiV1(HttpServletRequest request) {
        //转发内部业务接口
        String path = request.getHeader("X-Path");
        path = "/" + path.replaceAll("\\.", "/");

        //签名鉴权
         verify(request,path,true);
        //流量控制
        flowControl(request,path);

        //有问题直接跳转异常访问接口  TODO
        log.info("========校验通过转发请求path====>{}",path);

        return path;
    }

    @Override
    public String openapiV0(HttpServletRequest request) {
        //转发内部业务接口
        String path = request.getHeader("X-Path");
        path = "/" + path.replaceAll("\\.", "/");
        //签名鉴权
        verify(request,path,false);
        //流量控制
        flowControl(request,path);

        //有问题直接跳转异常访问接口  TODO
        log.info("========校验通过转发请求path====>{}",path);

        return path;
    }


    /**
     * 参数规范
     * @param path
     */
    private void paramsStandard(String path) {

    }

    /**
     * 流量控制
     * @param path
     */
    private void flowControl(HttpServletRequest request,String path) {
        Integer maxLimit =null;
        String appKey = request.getHeader("X-App-Key");
        String uid = request.getHeader("X-Uid-Time");
        // 1、限制同一请求不能重复提交
        //同一请求：同一个appkey 同一个url 视为同一请求，限制多长时间内不能重复提交   (uuid 5分钟内uuid相同则视为同一请求)
        if(redisUtil.hasKey(uid)){
            throw new BootException("请勿重复提交");
        }else {
            redisUtil.set(uid,appKey,openApiConfig.getSameTime());
        }
        //  2、限制同一key一分钟内访问数量
        //通过redis去实现，key为appkey地址 value为访问次数   过期时间为1分钟

        // 看redis是否存在appkey
        if(redisUtil.hasKey(appKey)){
            //判断是否超过60次
            maxLimit=(Integer)redisUtil.get(appKey);
            if(maxLimit >= openApiConfig.getCount()){
                throw new BootException("请勿频繁访问");
            }
            //未超过 次数加一 重设过期时间一分钟
            redisUtil.set(appKey,maxLimit+1,openApiConfig.getExpireTime());
        }else {
            //redis中不存在 一段时间内第一次访问
            redisUtil.set(appKey,1,openApiConfig.getExpireTime());
        }
        //todo 3、待定

    }

    /**
     * 签名鉴权
     * 签名算法 HMAC_MD5_encode(SECRET, uuid + ":" + time);
     */
    private void verify(HttpServletRequest request,String path,boolean flag) {
        //appKey
        String appKey = request.getHeader("X-App-Key");
        //uuid + "," + time
        String msgId = request.getHeader("X-Uid-Time");

        String sign = request.getHeader("X-Sign");
        if (ObjectUtil.isEmpty(sign)){
            throw new BootException("X-Sign不能为空");
        }

        QueryWrapper<OpenApiApp> openApiAppQueryWrapper = new QueryWrapper<>();
        openApiAppQueryWrapper.lambda().eq(OpenApiApp::getAppkey,appKey);
        OpenApiApp openApiApp = openApiAppMapper.selectOne(openApiAppQueryWrapper);
        if (ObjectUtil.isEmpty(openApiApp)){
            throw new BootException("appkey非法");
        }
        //1 正常 2 禁用 3 删除',
        if (ObjectUtil.equal(openApiApp.getStatus(),2)){
            throw new BootException("appkey已禁用");
        }
        if (ObjectUtil.equal(openApiApp.getStatus(),3)){
            throw new BootException("appkey已删除");
        }

        if(flag){
            //严格验证 X-Sign
            String localSign = "";
            try {
                localSign = CmdUtils.HMACMD5Encode(openApiApp.getAppsecrect(), msgId.replaceAll(",",":"));
            }catch (Exception e){
                throw new BootException("签名信息错误");
            }
            log.info("localSign==>{},x-sign=={}",localSign,sign);
            if (ObjectUtil.isEmpty(localSign) || !sign.equals(localSign)){
                throw new BootException("无权访问");
            }
        }

        /**
         * 授权接口校验
         */
        //对外开放的接口校验  配置文件中获取
        //改写原有方法 使其适用正则表达式
        boolean isAuth = Optional.ofNullable(openApiConfig.getOpenApi()).orElse(ListUtil.empty()).stream().anyMatch(ignoreUrl -> match(ignoreUrl,path));

        if (!isAuth) {
            throw new BootException("此服务未开放,不得访问");
        }

        //已授权接口校验
        QueryWrapper<OpenApiAppPermission> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(OpenApiAppPermission::getApiId, openApiApp.getId());
        List<OpenApiAppPermission> permissions = openApiAppPermissionMapper.selectList(queryWrapper);
        if (ObjectUtil.isEmpty(permissions)) {
            throw new AuthorizationException("未经授权服务");
        }

        long count = permissions.stream().filter(x -> ObjectUtil.equal(path, x.getUrl())).count();
        if (count == 0){
            throw new AuthorizationException("未授权此服务，不得访问");
        }
    }



    private static PathMatcher matcher = new AntPathMatcher();
    public static boolean match(String patternPath, String requestPath) {
        if (StringUtil.isEmpty(patternPath) || StringUtil.isEmpty(requestPath)) {
            return false;
        }
        return matcher.match(patternPath, requestPath);
    }



    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
        String accessKey ="sdiok34ewf";
        String secretKey ="uivnfdg89gtr";
        String url ="http://localhost:8080/jeecg-boot/openapi/v1";
        url = "http://123.56.42.177:32530/jeecg-boot/openapi/v1";
        HashMap<String, String> head = new HashMap<> ();
        head.put ("Content-Type", "application/json");
        head.put ("X-App-Key", accessKey);
        head.put ("X-Path", "api2021.sumErrorMsg");

        String uuid = UUID.randomUUID ().toString ();
        long l = System.currentTimeMillis();
        String token = CmdUtils.HMACMD5Encode (secretKey, uuid + ":" + l);
        head.put ("X-Uid-Time", uuid + "," + l);
        head.put ("X-Sign", token);

        HttpResponse execute = HttpUtil.createGet(url)
                .headerMap(head, true)
                .execute();
        System.out.println(execute.getStatus());
        System.out.println(execute.body());
    }



}
